从旧版 Rive Android 运行时迁移
概述
新的 Rive Android 运行时是对公共 API 和内部架构的近乎完全的重写。虽然在概念上大多数操作都有等效项,但两个 API 不兼容。你在旧版 API 中任何想迁移的现有工作都必须使用新的 API 重新构建。本指南旨在演示如何迁移每个功能区域。
本指南涵盖:
- 共享功能 - 常见操作及其等效项。
- 新的独占功能 - 仅在新运行时中可用的功能。
- 旧版独占功能 - 新运行时不再支持的功能。大多数都有变通方法或替代方案;这些移除简化了 API 表面,减少了维护开销,并消除了反模式。
本指南并非详尽无遗,因为那样会与现有的通用文档重复。有关特定主题的更多详细信息,请参阅文档的相关部分。
包
旧版 Rive Android 运行时位于 app.rive.runtime.kotlin 包(+ .core)中。新运行时位于 app.rive 包(+ .core)中。这允许同名类(如 Artboard)共存而不冲突。如果你需要在同一文件中使用两个包中的同名类,可以使用 Kotlin 的 import as 功能为其中一个导入添加别名。
import app.rive.Artboard
import app.rive.runtime.kotlin.core.Artboard as LegacyArtboard
异步 API
由于基于 RiveWorker 的新线程模型,几乎所有在主线程上原本同步返回值的操作现在都是异步的 suspend 函数,例如加载 Rive 文件和执行查询。你需要从协程上下文中调用这些函数,例如在 Compose 的 LaunchedEffect 中,或使用 Activity 或 Fragment 中的 lifecycleScope.launch。
最常见的 Compose 操作被包装在辅助函数中,例如 rememberRiveFile,它将异步状态公开为 Result 类型以便在可组合项中更易于使用。其他操作是"即发即弃"的,例如从加载的 Rive 文件创建画板和视图模型实例,或推进状态机。
生命周期
旧版 Rive Android 运行时基于 View,RiveAnimationView 类根据所属 Activity 或 Fragment 的生命周期管理其生命周期。Rive 文件的生命周期与视图的生命周期相关联,渲染器、控制器以及创建的对象(如画板和状态机)也是如此。这些使用分层引用计数来管理其生命周期。
新运行时使用 AutoCloseable 接口来显式管理对象(如 Rive 文件、画板和状态机)的生命周期。相比之下,worker 使用引用计数,并拥有其所创建资源的所有内存。手动创建对象时, 你必须 close 它们以释放其资源,否则它们将在 worker 的生命周期内持续存在。或者,你可以将它们与 use 辅助函数一起使用,该函数将在其块末尾调用 close。worker 本身必须释放所有引用,否则它及其资源将会泄漏。
在 Compose 中,生命周期与 remember 可组合项绑定,并使用 DisposableEffect 根据需要创建和处置对象,简化了内存管理。
共享功能
共享的 API
一些 API 在旧版和新运行时之间是共享的。它们仍然位于旧版运行时的 app.rive.runtime.kotlin 包中。
- 用于初始化的
Rive全局对象,包括Rive.init方法。 RiveInitializerXML 初始化辅助工具。- 回退字体 API,包括
FontFallbackStrategy接口和FontFallbackStrategy.stylePicker全局变量。
这些共享的 API 将来可能会发生变化,变为在新包中具有修订版本的完全分离。这只会在主要版本发布时作为破坏性变更发生。
RiveAnimationView 到 Rive
旧版 Rive Android 运行时基于 View,提供了 RiveAnimationView 类来显示 Rive 动画。 该名称反映了其创建时较有限的功能集,主要用于动画播放。
新 API 基于 Compose,计划将来提供 View API。主要的可组合项简称为 Rive,反映了其支持的更广泛的功能集和用例。
设置本地原始资源
新 API 将默认使用 I/O 调度器异步加载 Rive 文件。这与旧版运行时不同,旧版运行时在主线程上同步加载文件。如果你想使用不同的线程模型加载文件,请使用你首选的方法加载资源,并将字节传递给 RiveFileSource.Bytes 源。
旧版 RiveAnimationView
// Builder API
val riveView = RiveAnimationView.Builder(context)
.setResource(R.raw.my_rive_file)
.build()
// Set 方法
val riveView = RiveAnimationView(context)
riveView.setRiveResource(R.raw.my_rive_file)
旧版 XML 布局
<app.rive.runtime.kotlin.RiveAnimationView
app:riveResource="@raw/my_rive_file" />
新 Rive 可组合项
val riveWorker = rememberRiveWorker()
val riveFile = rememberRiveFile(
RiveFileSource.RawRes.from(R.raw.my_rive_file),
riveWorker
)
when (riveFile) {
is Result.Success -> Rive(riveFile.value)
else -> {} // 适当处理加载和错误状态
}
从字节设置 Rive 文件
旧版 RiveAnimationView
// Builder API
val riveView = RiveAnimationView.Builder(context)
.setResource(myBytes)
.build()
// Set 方法
val riveView = RiveAnimationView(context)
riveView.setRiveBytes(myBytes)
新 Rive 可组合项
val riveWorker = rememberRiveWorker()
val riveFile = rememberRiveFile(
RiveFileSource.Bytes(myBytes),
riveWorker
)
when (riveFile) {
is Result.Success -> Rive(riveFile.value)
else -> {} // 适当处理加载和错误状态
}
缓 存 Rive 文件
有关更多详细信息,另请参阅缓存 Rive 文件。
在新运行时中,Rive 文件始终是显式创建的,而不是通常由视图隐式创建。这使得在多个 Rive 实例之间缓存和重用 Rive 文件更加明显。
旧版 RiveAnimationView
// 为简单起见在主线程加载字节;在生产环境中考虑在后台线程加载。
val bytes = resources.openRawResource(R.raw.my_rive_file).use { res -> res.readBytes() }
val riveFile = File(bytes)
// Builder API
val riveView = RiveAnimationView.Builder(context)
.setResource(riveFile)
.build()
// Set 方法
val riveView = RiveAnimationView(context)
riveView.setRiveFile(riveFile)
// 如果不再需要该文件则释放,如果计划重用它则保留。
riveFile.release()
新 Rive 可组合项
由 rememberRiveFile 生成的 Rive 文件可以被提升到更高级别的可组合项,以存活更长时间并在多个 Rive 实例之间共享。
val riveWorker = rememberRiveWorker()
val riveFile = rememberRiveFile(
RiveFileSource.RawRes.from(R.raw.my_rive_file),
riveWorker
)
when (riveFile) {
is Result.Success -> Rive(riveFile.value)
else -> {} // 适当处理加载和错误状态
}
选择画板和状态机
旧版 RiveAnimationView
// Builder API
val riveView = RiveAnimationView.Builder(context)
.setResource(R.raw.my_rive_file)
.setArtboardName("My Artboard")
.setStateMachineName("My State Machine")
.build()
// Set 方法
val riveView = RiveAnimationView(context)
riveView.setRiveResource(R.raw.my_rive_file,
artboardName = "My Artboard",
stateMachineName = "My State Machine"
)
旧版 XML 布局
<app.rive.runtime.kotlin.RiveAnimationView
app:riveResource="@raw/my_rive_file"
app:riveArtboard="My Artboard"
app:riveStateMachine="My State Machine" />
新 Rive 可组合项
画板和状态机作为对象而不是名称传递。你可以使用 rememberArtboard 和 rememberStateMachine 辅助函数从加载的 RiveFile 获取这些对象。
val riveWorker = rememberRiveWorker()
val riveFile = rememberRiveFile(
RiveFileSource.RawRes.from(R.raw.my_rive_file),
riveWorker
)
when (riveFile) {
is Result.Success -> {
val artboard = rememberArtboard(riveFile.value, "My Artboard")
val stateMachine = rememberStateMachine(artboard, "My State Machine")
Rive(
riveFile.value,
artboard = artboard,
stateMachine = stateMachine
)
}
else -> {} // 适当处理加载和错误状态
}
设置适配和对齐
有关 更多详细信息,另请参阅布局文档。
旧版 RiveAnimationView
// Builder API
val riveView = RiveAnimationView.Builder(context)
.setResource(R.raw.my_rive_file)
.setFit(Fit.CONTAIN)
.setAlignment(Alignment.CENTER)
.build()
// Set 方法
val riveView = RiveAnimationView(context)
riveView.setRiveResource(R.raw.my_rive_file,
fit = Fit.CONTAIN,
alignment = Alignment.CENTER
)
// 属性
riveView.controller.fit = Fit.CONTAIN
riveView.controller.alignment = Alignment.CENTER
旧版 XML 布局
<app.rive.runtime.kotlin.RiveAnimationView
app:riveResource="@raw/my_rive_file"
app:riveFit="CONTAIN"
app:riveAlignment="CENTER" />
新 Rive 可组合项
新运行时将适配模式建模为密封类,仅在支持对齐的变体上允许设置对齐。此外,Layout 变体公开了布局缩放因子。
Rive(
riveFile,
fit = Fit.Contain(Alignment.Center),
)
自动绑定
有关更多详细信息,另请参阅数据绑定中的自动绑定部分。
旧版 RiveAnimationView
// Builder API
val riveView = RiveAnimationView.Builder(context)
.setResource(R.raw.my_rive_file)
.setAutoBind(true)
.build()
// Set 方法
val riveView = RiveAnimationView(context)
riveView.setRiveResource(R.raw.my_rive_file, autoBind = true)
旧版 XML 布局
<app.rive.runtime.kotlin.RiveAnimationView
app:riveResource="@raw/my_rive_file"
app:riveAutoBind="true" />
新 Rive 可组合项
由于可组合项的性质,Compose API 中没有实现自动绑定。由于它们是函数,与类相比很难从中获取值。回调需要一个空占位符来在触发前记住值,这比直接提供实例产生更多开销。
等效做法是创建一个无源的视图模型实例。这将在内部创建默认画板、该画板的默认视图模型以及该视图模型的默认实例。然后可以将其传递给 Rive 可组合项。
val vmi = rememberViewModelInstance(riveFile)
Rive(
riveFile,
viewModelInstance = vmi,
)
播放、暂停和状态机稳定
旧版 RiveAnimationView
旧版 API 在 RiveAnimationView 类上包含 play() 和 pause() 方法来控制动画的播放。值得注意的是,暂停状态与稳定状态相同,这并不理想。play() 将同时取消暂停和取消稳定状态机,使其推进。
riveView.play() // 取消稳定并播放
riveView.pause() // 暂停
新 Rive 可组合项
在新运行时中,没有 play 或 pause 方法。相反,播放状态的控制通过 Rive 可组合项上的 playing 参数完成。将 playing 设置为 false 将暂停动画,而将其设置为 true 将恢复播放。状态机的稳定和取消稳定是自动完成的,涵盖了所有需要它的场景。
var isPlaying by remember { mutableStateOf(true) }
Rive(
riveFile,
playing = isPlaying
)
自动播放和设置初始值
旧版运行时包含一个 autoplay 功能(默认为 true),在加载 Rive 文件时自动开始播放给定的动画或状态机。可以禁用它以在播放开始前设置初始值。
旧版 RiveAnimationView
// Builder API
val riveView = RiveAnimationView.Builder(context)
.setResource(R.raw.my_rive_file)
.setAutoPlay(true)
.build()
// Set 方法
val riveView = RiveAnimationView(context)
riveView.setRiveResource(R.raw.my_rive_file, autoPlay = true)
旧版 XML 布局
<app.rive.runtime.kotlin.RiveAnimationView
app:riveResource="@raw/my_rive_file"
app:riveAutoPlay="true" />
新运行时没有包含 autoplay 功能,因为它不是必需的——Rive 文件与其展示是解耦的。你可以将 playing 保留为其默认值 true 以实现类似行为。要在播放开始前设置初始值,可以将 playing 设置为 false,更新数据绑定的属性,然后将 playing 设置为 true 开始播放。
var isPlaying by remember { mutableStateOf(false) }
val vmi = rememberViewModelInstance(riveFile)
LaunchedEffect(vmi) {
// 设置初始值
vmi.setNumber("My Number Property", 42.0f)
// 开始播放
isPlaying = true
}
Rive(
riveFile,
playing = isPlaying,
viewModelInstance = vmi,
)
触摸穿透
旧版 RiveAnimationView
// Builder API
val riveView = RiveAnimationView.Builder(context)
.setResource(R.raw.my_rive_file)
.setTouchPassThrough(true)
.build()
// 属性
riveView.touchPassThrough = true
旧版 XML 布局
<app.rive.runtime.kotlin.RiveAnimationView
app:riveResource="@raw/my_rive_file"
app:riveTouchPassThrough="true" />
新 Rive 可组合项
新运行时在 Rive 可组合项上提供了 touchPassThrough 参数。这更加细致,可以设置为 Consume(默认行为)、Observe 或 PassThrough。Observe 仅与 Compose 层次结构中的祖先共享,适用于不阻止滚动事件,而 PassThrough 还与同级共享(等效于旧版行为)。
Rive(
riveFile,
touchPassThrough = RivePointerInputMode.PassThrough,
)
视图模型
旧版
旧版运行时包含一个从 File 检索的 ViewModel 类,对应于 Rive 编辑器中的视图模型。此类提供了查询和创建视图模型实例的方法。
val viewModel = riveFile.getViewModelByName("My View Model")
val instance = viewModel.createInstanceFromName("My Instance")
val properties = viewModel.properties
新版
新运行时将查询合并到 RiveFile 类中,在查询需要时包含视图模型名称。视图模型实例由 ViewModelSource 指定,可以是 Named 或 DefaultForArtboard。然后,该 ViewModelSource 可以使用 blankInstance、defaultInstance 或 namedInstance 辅助函数转换为 ViewModelInstanceSource。完成的源可以传递给 rememberViewModelInstance 或 ViewModelInstance.fromFile。
val instanceSource = ViewModelSource.Named("My View Model").namedInstance("My Instance")
val instance = rememberViewModelInstance(riveFile, instanceSource)
LaunchedEffect(riveFile) {
val properties = riveFile.getViewModelProperties("My View Model")
}
视图模型实例属性
旧版
旧版运行时在 ViewModelInstance 类上提供显式的属性对象,用于查询和更新数据绑定属性。
// 获取属性
val numberProperty = vmi.getNumberProperty("My Number Property")
// 读取其值
val numberValue = numberProperty.value
// 观察其值
numberProperty.valueFlow.collect { value ->
// 处理值更新
}
// 写入其值
numberProperty.value = 42.0f
新版
新运行时不提供显式的属性对象。相反,属性通过 getter 和 setter 方法按名称在 ViewModelInstance 上查询和更新。
LaunchedEffect(vmi) {
// 读取属性值
val numberValue = vmi.getNumberFlow("My Number Property").first()
// 观察其值
vmi.getNumberFlow("My Number Property").collect { value ->
// 处理值更新
}
}
// 在 Compose 中观察
val numberValue by vmi.collectNumberAsState("My Number Property", initial = 0.0f)
// 写入属性值
vmi.setNumber("My Number Property", 42.0f)
可绑定画板
旧版
旧版运行时包含一个 BindableArtboard 类,该类包装了原生 Artboard 并提供数据绑定功能。此类旨在限制 API 表面并通过限制对底层画板的直接访问来防止反模式。
val bindableArtboard = riveFile.createBindableArtboardByName("My Artboard")
val artboardProperty = vmi.getArtboardProperty("My Artboard Property")
artboardProperty.set(bindableArtboard)
新版
新运行时没有包含 BindableArtboard 类。相反,两种情况使用相同的 Artboard 类。
val artboard = rememberArtboard(riveFile, "My Artboard")
vmi.setArtboard("My Artboard Property", artboard)
加载引用资源
旧版
旧版运行时使用"拉取"模型来加载引用资源,运行时通过回调按需请求资源(通过 FileAssetLoader 类或其子类)。这允许延迟加载资源,但移除了用户对资源何时加载的控制。
旧版 RiveAnimationView
class MyAssetLoader(private val context: Context) : ContextAssetLoader(context) {
// 处理一个名为 "my_image" 的图片资源的示例资源加载器
override fun loadContents(asset: FileAsset, inBandBytes: ByteArray): Boolean {
val identifier =
if (asset.uniqueFilename == "my_image") R.raw.my_image
else return false
context.resources.openRawResource(identifier).use {
asset.decode(it.readBytes())
}
return true
}
}
// Builder API
val riveView = RiveAnimationView.Builder(context)
.setResource(R.raw.my_rive_file)
.setAssetLoader(MyAssetLoader(context))
.build()
// Set 方法
val riveView = RiveAnimationView(context)
riveView.setAssetLoader(MyAssetLoader(context))
riveView.setRiveResource(R.raw.my_rive_file)
旧版 XML 布局
<app.rive.runtime.kotlin.RiveAnimationView
app:riveResource="@raw/my_rive_file"
app:riveAssetLoaderClass="com.example.MyAssetLoader" />
新版
新运行时使用"推送"模型,应用程序负责在需要之前将引用资源提供给 worker。如果在需要之后才提供资源,它们将"弹出"显示。此更改旨在简化内部架构,并允许用户灵活地以他们选择的任何方式加载资源。
目前获取所需资源列表的方法是通过检查导出的 .zip 中的文件。将来我们将添加一个 API 来从 Rive 文件中查询所需资源的清单。
资源对 worker 是"全局的",这意味着它们可以在多个 Rive 文件和实例之间共享,只要它们具有相同的键。这减少了常见资源(如字体)的内存使用。
val riveWorker = rememberRiveWorker()
val bytesResult = produceState<Result<ByteArray>>(Result.Loading) {
value = withContext(Dispatchers.IO) {
context.resources.openRawResource(R.raw.my_image)
.use { Result.Success(it.readBytes()) }
}
}.value
val image = bytesResult.andThen { bytes -> rememberRegisteredImage(riveWorker, "My Image", bytes) }
默认布局缩放因子
旧版运行时使用报告的设备密度作为以 Layout 作为适配模式渲染 Rive 时的默认缩放因子 。
新运行时使用默认缩放因子为 1.0,意味着 Rive 单位直接映射到屏幕像素。在实践中,匹配密度通常会导致视觉效果过大。但是,如果这是期望的行为并且你想匹配旧版运行时,可以使用 LocalDensity 将缩放因子设置为设备密度。
val density = LocalDensity.current.density
Rive(
riveFile,
fit = Fit.Layout(scaleFactor = density)
)
新的独占功能
越来越多的功能仅在新运行时中可用。这些功能是为了解决旧版运行时的限制或提供以前不可能的新功能而添加的。以下是这些功能的列表及简要说明。
日志记录
新运行时包含一个细粒度、灵活的日志记录系统,允许你在不同级别(debug、info、warning、error)捕获日志,并将其重定向到你偏好的日志框架或接收器。
相比之下,旧版运行时的日志记录有限。
我们将来可能会考虑为旧版运行时改造更多日志记录能力,特别是如果这样做能揭示潜在的错误来源。
跟踪 Rive 文件的加载状态
新运行时提供了一个围绕 Rive 文件加载操作的 Result 包装器,允许你跟踪 Rive 文件的状态(加载中、成功、错误)。这对于在加载期间提供用户反馈或妥善处理 错误非常有用。
val riveFile = rememberRiveFile(
RiveFileSource.RawRes.from(R.raw.my_rive_file),
riveWorker
)
when (riveFile) {
is Result.Loading -> {
// 显示加载指示器
}
is Result.Failure -> {
// 显示错误消息
}
is Result.Success -> {
Rive(riveFile.value)
}
}
旧版运行时的便捷方法(如 setRiveResource())不提供直接跟踪加载状态或处理错误的方式。相反,必须首先使用 File 类加载文件。加载通常是同步的,但可以在后台线程上执行。通过一些努力,可以实现类似的加载状态跟踪。
目前没有计划将此功能改造到旧版运行时中。
渲染到 Bitmap
新运行时提供了内置支持,用于将 Rive 渲染到 Android Bitmap,这对于快照测试或将数据绑定的 Rive 文件渲染成图像帧以在边缘(即用户设备上)编码视频等场景非常有用。这可以通过 RenderBuffer 类或 Rive 可组合项中的 onBitmapAvailable 回调来实现。
在旧版运行时中,通过渲染到由 Bitmap 支持的 Canvas 或子类化渲染器,在技术上是可行的,但这不是内置功能,需要更多努力来实现。
目前没有计划将此功能改造到旧版运行时中。
更新数据绑定会取消稳定状态机
在新运行时中,更新数据绑定属性会自动"取消稳定"状态机,使其根据新数据推进和绘制。相比之下,旧版运行时要求你在稳定状态机上更新数据绑定属性后调用 RiveAnimationView.play() 才能达到相同的效果。
我们将来可能会考虑将此行为改造到旧版运行时中,因为这是常见的困惑来源。然而,这样做将是一个破坏性变更,对于现有用户来说可能不值得因此而中断。
旧版独占功能
一些功能仅在旧版运行时中可用。这通常是 因为它们不太常用、在实践中复杂或难以维护和支持。以下是这些功能的列表及如何在新运行时中处理其缺失的指导。
基于 View 的 API
旧版运行时围绕 RiveAnimationView 类构建,该类是 Android TextureView(进而 View)的子类。这使得集成到现有的基于 View 的 Android 应用程序中变得容易。新运行时目前基于 Compose,不提供 View 子类。如果你需要在基于 View 的应用程序中使用 Rive,你需要使用 ComposeView 在你的 View 层次结构中设置 Compose 环境。
有计划在未来提供基于 View 的 API。
帧指标
旧版运行时支持帧指标,提供一些渲染性能数据,可能有助于识别瓶颈。这通过启用 RiveAnimationView 上的帧指标收集来完成。
riveView.startFrameMetrics()